#include "process.h"
#include "bitmap.h"
#include "system.h"
#include "thread.h"
#include "addrspace.h"
#include "synch.h"
#include "utility.h"
#include "syscall.h"

ProcessTable processTable;


int copyin(unsigned int vaddr, int len, char *buf) {
	//DEBUG('p', "[KERNEL PID#%i, TID%i] copyin() started\n", currentThread->getProcessID(), currentThread->getThreadID());
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    bool result;
    int n=0;			// The number of bytes copied in
    int *paddr = new int;
    ASSERT(paddr != NULL);
    while ( n >= 0 && n < len) {
      result = machine->ReadMem( vaddr, 1, paddr );
      while(!result) // FALL 09 CHANGES
	  {
   			result = machine->ReadMem( vaddr, 1, paddr ); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
	  }

      buf[n++] = *paddr;
      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    delete paddr;
    //DEBUG('p', "[KERNEL PID#%i, TID%i] copyin() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
	//DEBUG('p', "[KERNEL PID#%i, TID%i] copyout() started\n", currentThread->getProcessID(), currentThread->getThreadID());
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n=0;			// The number of bytes copied in

    while ( n >= 0 && n < len) {
      // Note that we check every byte's address
      result = machine->WriteMem( vaddr, 1, (int)(buf[n++]) );

      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }
    //DEBUG('p', "[KERNEL PID#%i, TID%i] copyout() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
    return n;
}

void Kernel_Thread_Func(unsigned int funcVAddr)
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() started\n", currentThread->getProcessID(), currentThread->getThreadID());
	currentThread->space->InitRegisters();		// set the initial register values
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() initialized the registers\n", currentThread->getProcessID(), currentThread->getThreadID());
	machine->WriteRegister(PCReg,funcVAddr);
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() set the PC reg\n", currentThread->getProcessID(), currentThread->getThreadID());
	machine->WriteRegister(NextPCReg,funcVAddr+4);
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() set the pc next reg\n", currentThread->getProcessID(), currentThread->getThreadID());
	currentThread->space->RestoreState();		// load page table register
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() restored the state\n", currentThread->getProcessID(), currentThread->getThreadID());
	machine->WriteRegister(StackReg, currentThread->getThreadStack());
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() set the stack register\n", currentThread->getProcessID(), currentThread->getThreadID());
	machine->Run();
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Thread_Func() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
}

void Kernel_Process_Func(int processID)
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Process_Func() started\n", currentThread->getProcessID(), currentThread->getThreadID());
	currentThread->space->InitRegisters();		// set the initial register values
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Process_Func() initialized the registers\n", currentThread->getProcessID(), currentThread->getThreadID());
	currentThread->space->RestoreState();		// load page table register
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Process_Func() restored the state\n", currentThread->getProcessID(), currentThread->getThreadID());
	machine->Run();			// jump to the user progam
	DEBUG('p', "[KERNEL PID#%i, TID%i] Kernel_Process_Func() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
	ASSERT(FALSE);			// machine->Run never returns;
							// the address space exits
							// by doing the syscall "exit"
}

void ProcessEntry::Exit(int threadID)
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) started\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
	ASSERT(threadID>=0 && threadID<MaxThreadsPerProcess);
	if (_threadBitMap->Test(threadID))
	{
		//Lets grab the thread
		Thread* thread = _threadTable[threadID];
		//lets wait for children to finish first
		DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) lets wait for child threads to finish\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
		thread->threadLock->Acquire();
		thread->_waitingToFinish = true;
		while( (thread->_numOfChildThreads) > 0)
		{
			thread->threadCV->Wait(thread->threadLock);
		}
		thread->threadLock->Release();
		//lets remove this thread from the thread list of this process
		DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) lets remove this thread from thread list of process\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
		_threadBitMapLock->Acquire();
		_threadBitMap->Clear(threadID);
		_threadBitMapLock->Release();
		//lets tell the parent of this thread that one of its children is done
		DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) lets tell the parent thread that we are leaving\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
		int parentThreadID = thread->getParentThreadID();
		if (parentThreadID > -1)
		{
			ASSERT(parentThreadID>=0 && parentThreadID<MaxThreadsPerProcess);
			if (_threadBitMap->Test(parentThreadID))
			{
				Thread* parentThread = _threadTable[parentThreadID];
				DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) found the parent thread. lets signal it if it is waiting\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
				parentThread->threadLock->Acquire();
				parentThread->_numOfChildThreads--;
				if (parentThread->_numOfChildThreads <= 0)
				{
					if (parentThread->_waitingToFinish) {
						parentThread->threadCV->Signal(parentThread->threadLock);
					}
				}
				parentThread->threadLock->Release();
			}
			else
			{
				printf("KERNEL Error: Request to finish a thread with an invalid parent thread ID.\n");
				interrupt->Halt();
			}
		}
		DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) lets wait for ever.\n", currentThread->getProcessID(), currentThread->getThreadID());
		//while(true)
				//currentThread->Yield();
		//lets grab the process info
		if (thread->getProcessID() == -1) //this is the main thread
		{
			//Lets wait for the child processes to finish
			DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) This is the main thread. lets wait for child processes to finish\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
			_processLock->Acquire();
			while (_numOfChildProcesses > 0){
				_waitingToFinish = true;
				_processCV->Wait(_processLock);
			}
			_waitingToFinish = false;
			_processLock->Release();
			printf("KERNEL: Finished the last thread of the last process.\nHalting the system!\n");
			interrupt->Halt();
		}
		else
		{
			//Tell the parent process that we are finished
			if (_parentProcessId!=-1)
			{
				DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) tell the parent process#%i that we are finished\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID, _parentProcessId);
				ProcessEntry* parentProcessEntry = processTable.getProcess(_parentProcessId);
				ASSERT(parentProcessEntry != NULL);
				parentProcessEntry->_processLock->Acquire();
				(parentProcessEntry->_numOfChildProcesses)--;
				if (parentProcessEntry->_waitingToFinish)
				{
					parentProcessEntry->_processCV->Signal(parentProcessEntry->_processLock);
				}
				parentProcessEntry->_processLock->Release();
			}
			DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) call finish in the thread\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
			thread->Finish();
		}
	}
	else
	{
		printf("KERNEL Error: Request to finish an invalid thread.\n");
		interrupt->Halt();
	}
	DEBUG('p', "[KERNEL PID#%i, TID%i] ProcessEntry::Exit(thread %i) finished\n", currentThread->getProcessID(), currentThread->getThreadID(), threadID);
}

Thread* ProcessEntry::getThread(int threadID)
{
	ASSERT(threadID>=0 && threadID<MaxThreadsPerProcess);
	if (_threadBitMap->Test(threadID))
		return _threadTable[threadID];
	else
		return NULL;
}
char* ProcessEntry::getFileName()
{
	return _fileName;
}
AddrSpace* ProcessEntry::getAddressSpace()
{
	return _addressSpace;
}
void ProcessEntry::SetAddressSpace(AddrSpace* newAddrSpace)
{
	_currentProcessLock->Acquire();
	_addressSpace = newAddrSpace;
	_currentProcessLock->Release();
}

int ProcessEntry::StackStartPage(int processID, int threadID, int numOfPages)
{
	DEBUG('p', "[KERNEL PID#%i, TID#%i] ProcessEntry::StackStartPage(processID#%i,thread#%i) started\n", currentThread->getProcessID(), currentThread->getThreadID(),processID, threadID);
	ASSERT(_addressSpace != NULL);
	_currentProcessLock->Acquire();
	int result = (numOfPages - (UserStackSize/PageSize)*(MaxThreadsPerProcess-threadID-1)) * PageSize - 16;
	_currentProcessLock->Release();
	DEBUG('p', "[KERNEL PID#%i, TID#%i] ProcessEntry::StackStartPage(processID#%i,thread#%i) finished. Result is %i\n", currentThread->getProcessID(), currentThread->getThreadID(),processID, threadID, result);
	return result;
}

void ProcessEntry::ReleaseResources()
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] ReleaseResources() started\n", currentThread->getProcessID(), currentThread->getThreadID());
	if (_threadBitMap != NULL)
	{
		delete _threadBitMap;
		_threadBitMap= NULL;
	}
	if (_threadBitMapLock != NULL)
	{
		delete _threadBitMapLock;
		_threadBitMapLock = NULL;
	}
	if (_numOfThreadsLock != NULL)
	{
		delete _numOfThreadsLock;
		_numOfThreadsLock = NULL;
	}
	if (_currentProcessLock != NULL)
	{
		delete _currentProcessLock;
		_currentProcessLock = NULL;
	}
	if (_childProcessBitMap != NULL)
	{
		delete _childProcessBitMap;
		_childProcessBitMap = NULL;
	}
	if (_childProcessBitMapLock != NULL)
	{
		delete _childProcessBitMapLock;
		_childProcessBitMapLock = NULL;
	}
	if (_processCV != NULL)
	{
		delete _processCV;
		_processCV = NULL;
	}
	if (_processLock != NULL)
	{
		delete _processLock;
		_processLock = NULL;
	}
	DEBUG('p', "[KERNEL PID#%i, TID%i] ReleaseResources() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
}


void ProcessEntry::Initialize()
{
	_threadBitMap = new BitMap(MaxThreadsPerProcess);
	ASSERT(_threadBitMap != NULL);
	_threadBitMapLock = new Lock("ThdBMLock");
	ASSERT(_threadBitMapLock!=NULL);
	_numOfThreadsLock = new Lock("NumThreadLock");
	ASSERT(_numOfThreadsLock!=NULL);
	_currentProcessLock = new Lock("CurProcLock");
	ASSERT(_currentProcessLock!=NULL);
	_childProcessBitMap = new BitMap(MaxProcess);
	ASSERT(_childProcessBitMap != NULL);
	_childProcessBitMapLock =  new Lock("ChildPrcLock");
	ASSERT(_childProcessBitMapLock != NULL);
	_processLock =  new Lock("FinLock");
	ASSERT(_processLock != NULL);
	_processCV =  new Condition("FinCV");
	ASSERT(_processCV != NULL);
}

ProcessEntry::ProcessEntry(int processID, char* fileName, SpaceId parentProcessId):
											_processID(processID),
											_fileName(fileName),
											_addressSpace (NULL),
											_numOfChildProcesses(0),
											_parentProcessId(parentProcessId),
											_waitingToFinish(false),
											_numOfChildThreads(0)

{
	Initialize();
}


void ProcessEntry::StartProcess()
{
	ASSERT(_fileName != NULL);
	DEBUG('p', "[KERNEL PID#%i, TID%i] StartProcess(%s) started\n", currentThread->getProcessID(), currentThread->getThreadID(), _fileName);
	OpenFile *executable = fileSystem->Open(_fileName);
	if (executable == NULL) {
		printf("Unable to open file %s\n", _fileName);
		return;
	}
	//printf("Start process %i\n", _processID);
	_addressSpace = new AddrSpace(executable);
	DEBUG('p', "[KERNEL PID#%i, TID%i] StartProcess(%s) created address space\n", currentThread->getProcessID(), currentThread->getThreadID(), _fileName);
	ASSERT(_addressSpace!=NULL);
	_addressSpace->processID = _processID;
	delete executable;			// close file
	_threadBitMapLock->Acquire();
	int threadID = _threadBitMap->Find();
	_threadBitMapLock->Release();
	ASSERT(threadID > -1);
	_numOfThreadsLock->Acquire();
	_numOfChildThreads ++;
	_addressSpace->stackStart = StackStartPage(_processID, threadID, _addressSpace->getNumOfPages());
	DEBUG('p', "[KERNEL PID#%i, TID%i] StartProcess(%s) Creating the new process main thread\n", currentThread->getProcessID(), currentThread->getThreadID(), _fileName);
	_threadTable[threadID] = new Thread(_fileName, threadID, _processID, _addressSpace->stackStart,_addressSpace,-1); //-1 because there is no parent for this
	ASSERT(_threadTable[threadID]!=NULL);
	//Add my info into my parent unless this is the main startup thread
	if (_parentProcessId != -1)
	{
		ProcessEntry* parentProcessEntry = processTable.getProcess(_parentProcessId);
		ASSERT(parentProcessEntry != NULL);
		DEBUG('p', "[KERNEL PID#%i, TID%i] StartProcess(%s) Add info into parent PID#%i \n", currentThread->getProcessID(), currentThread->getThreadID(), _fileName, _parentProcessId);
		parentProcessEntry->_processLock->Acquire();
		(parentProcessEntry->_numOfChildProcesses)++;
		parentProcessEntry->_processLock->Release();
		parentProcessEntry->_childProcessBitMapLock->Acquire();
		int childProcessIndex = _childProcessBitMap->Find();
		parentProcessEntry->_childProcessBitMapLock->Release();
		ASSERT(childProcessIndex > -1);
		parentProcessEntry->_childProcessTable[childProcessIndex] = _processID;

	}
	_numOfThreadsLock->Release();
	DEBUG('p', "[KERNEL PID#%i, TID%i] StartProcess(%s) Forking the new process main thread\n", currentThread->getProcessID(), currentThread->getThreadID(), _fileName);
	_threadTable[threadID]->Fork((VoidFunctionPtr)Kernel_Process_Func,_processID);
}

ProcessEntry::~ProcessEntry()
{
	ReleaseResources();
}

int ProcessEntry::Fork(char* threadName, unsigned int funcVAddr)
{
	currentThread->threadLock->Acquire();
	if( currentThread->_waitingToFinish == true)
	{
		//cant continue since current thread is finishing
		printf("[KERNEL PID#%i, TID%i] ERROR: Cannot create a new %s thread because current thread is exiting\n",currentThread->getProcessID(), currentThread->getThreadID(), threadName);
		return -1;
	}
	(currentThread->_numOfChildThreads)++;
	currentThread->threadLock->Release();
	//
	//
	DEBUG('p', "[KERNEL PID#%i, TID%i] Fork(%s) started\n", currentThread->getProcessID(), currentThread->getThreadID(), threadName);
	_threadBitMapLock->Acquire();
	int threadID = _threadBitMap->Find();
	_threadBitMapLock->Release();
	DEBUG('p', "[KERNEL PID#%i, TID%i] Fork(%s,%i) Acquired the thread ID\n", currentThread->getProcessID(), currentThread->getThreadID(), threadName, threadID);
	ASSERT(threadID > -1);
	_numOfThreadsLock->Acquire();
	_numOfChildThreads ++; //for current process
	DEBUG('p', "[KERNEL PID#%i, TID%i] Fork(%s,%i) Create the new thread\n", currentThread->getProcessID(), currentThread->getThreadID(), threadName, threadID);
	_threadTable[threadID] = new Thread(threadName, threadID, _processID, StackStartPage(_processID,threadID, _addressSpace->getNumOfPages()),_addressSpace, currentThread->getThreadID());
	ASSERT(_threadTable[threadID]!=NULL);
	_numOfThreadsLock->Release();
	DEBUG('p', "[KERNEL PID#%i, TID%i] Fork(%s,%i) fork the new thread \n", currentThread->getProcessID(), currentThread->getThreadID(), threadName, threadID);
	_threadTable[threadID]->Fork((VoidFunctionPtr)Kernel_Thread_Func,funcVAddr);
	DEBUG('p', "[KERNEL PID#%i, TID%i] Fork(%s,%i) finished\n", currentThread->getProcessID(), currentThread->getThreadID(), threadName, threadID);
	return threadID;
}


ProcessTable::ProcessTable(void)
{
	_processBitMap = new BitMap(MaxProcess);
	ASSERT(_processBitMap!=NULL);
	_physicalMemoryBitMap = new BitMap(NumPhysPages);
	ASSERT(_physicalMemoryBitMap!=NULL);
	for (int i = 0; i < MaxProcess; i++)
	{
		_processLock[i] = new Lock("PrcLock");
		ASSERT(_processLock[i]!=NULL);
	}
	_processBitMapLock = new Lock("PTBitMapLock");
	ASSERT(_processBitMapLock!=NULL);
	_physicalMemoryBitMapLock = new Lock("PHysMemLock");
	ASSERT(_physicalMemoryBitMapLock!=NULL);
	_memoryLock = new Lock("MemLock");
	ASSERT(_memoryLock != NULL);
}

ProcessTable::~ProcessTable()
{
	ReleaseResources();
}

int ProcessTable::Exec(char* fileName, bool isMainProces = false)
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] Exec(%s,%s) started\n", currentThread->getProcessID(), currentThread->getThreadID(), fileName, (isMainProces)?"Main process":"Child process");
	_processBitMapLock->Acquire();
	int processID = _processBitMap->Find();
	_processBitMapLock->Release();
	if (processID < 0) {
		printf("Memory Overflow. Unable to create a process for %s\n", fileName);
		return -1;
	}
	DEBUG('p', "[KERNEL PID#%i, TID%i] Exec(%s,%s,%i) Acquired a new process ID\n", currentThread->getProcessID(), currentThread->getThreadID(), fileName, (isMainProces)?"Main process":"Child process",processID);
	_processLock[processID]->Acquire();
	if (isMainProces)
	{
		_processes[processID] =  new ProcessEntry(processID, fileName, -1);
	}
	else
	{
		_processes[processID] =  new ProcessEntry(processID, fileName, currentThread->getProcessID());
	}
	ASSERT(_processes[processID]!=NULL);
	_processLock[processID]->Release();
	DEBUG('p', "[KERNEL PID#%i, TID%i] Exec(%s,%s,%i) Created process entry. Lets Start it.\n", currentThread->getProcessID(), currentThread->getThreadID(), fileName, (isMainProces)?"Main process":"Child process",processID);
	_processes[processID]->StartProcess();
	DEBUG('p', "[KERNEL PID#%i, TID%i] Exec(%s,%s,%i) finished\n", currentThread->getProcessID(), currentThread->getThreadID(), fileName, (isMainProces)?"Main process":"Child process",processID);
	return processID;
}

void ProcessTable::ReleaseResources()
{
	DEBUG('p', "[KERNEL PID#%i, TID%i] ReleaseResources() started\n", currentThread->getProcessID(), currentThread->getThreadID());
	for (int i = 0; i<MaxProcess; i++)
	{
		if (_processes[i]!= NULL)
		{
			delete _processLock[i];
			_processLock[i] = NULL;
		}
		if (_processLock[i] != NULL)
		{
			delete _processLock[i];
			_processLock[i] = NULL;
		}
	}
	if (_processBitMap != NULL)
	{
		delete _processBitMap;
		_processBitMap = 0;
	}
	if (_processBitMapLock != NULL)
	{
		delete _processBitMapLock;
		_processBitMapLock = NULL;
	}
	if (_physicalMemoryBitMapLock != NULL)
	{
		delete _physicalMemoryBitMapLock;
		_physicalMemoryBitMapLock = NULL;
	}
	if (_physicalMemoryBitMap != NULL)
	{
		delete _physicalMemoryBitMap;
		_physicalMemoryBitMap = NULL;
	}
	if (_memoryLock != NULL)
	{
		delete _memoryLock;
		_memoryLock = NULL;
	}
	DEBUG('p', "[KERNEL PID#%i, TID%i] ReleaseResources() finished\n", currentThread->getProcessID(), currentThread->getThreadID());
}

int ProcessTable::getAvailablePhysicalPage()
{
	_physicalMemoryBitMapLock->Acquire();
	int pageID =_physicalMemoryBitMap->Find();
	_physicalMemoryBitMapLock->Release();
	return pageID;
}

ProcessEntry* ProcessTable::getProcess(int pid)
{
	ASSERT(pid>=0 && pid<MaxProcess);
	if (_processBitMap->Test(pid))
		return _processes[pid];
	else
		return NULL;
}
